home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-04-25 | 30.5 KB | 797 lines | [TEXT/CWIE] |
- //================================================================================
- // Greg Anderson
- // db+
- //
- // Group control object
- // 17 May 1994
- // 31 Dec 1994
- //================================================================================
-
- #include "GroupControlObject.h"
-
- #include "DatabaseDocument.h"
- #include "AbstractRecord.h"
-
- #include "Exceptions.h"
-
- //
- // For CopyMemory
- //
- #include "AbstractData.h"
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::~TGroupControlObject
- //--------------------------------------------------------------------------------
- TGroupControlObject::~TGroupControlObject()
- {
- } // TGroupControlObject::~TGroupControlObject
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::InitializeRecordCursors
- //--------------------------------------------------------------------------------
- void TGroupControlObject::InitializeRecordCursors()
- {
- for(short i=0; i<kRecordsPerGroup; ++i)
- fRecordCursors[i] = nil;
- } // TGroupControlObject::InitializeRecordCursors
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::DocumentKeySpace
- //--------------------------------------------------------------------------------
- Int64 TGroupControlObject::DocumentKeySpace()
- {
- return DBDocument()->ObjectsKeySpace();
- } // TGroupControlObject::DocumentKeySpace
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::CalculateRelativeIndex
- //--------------------------------------------------------------------------------
- long TGroupControlObject::CalculateRelativeIndex(long recordIndex) const
- {
- long relativeIndex = recordIndex - this->FirstRecordIndex();
-
- if((relativeIndex < 0) || (relativeIndex >= kRecordsPerGroup))
- Throw(eIndexOutOfRange);
-
- return relativeIndex;
- } // TGroupControlObject::CalculateRelativeIndex
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::NextRecordIndex
- //--------------------------------------------------------------------------------
- long TGroupControlObject::NextRecordIndex(long recordIndex) const
- {
- long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
- long nextIndex = recordIndex;
-
- //
- // If the data record indicator bits != 0, then this record
- // is a DB record; every DB record is one entry long, so
- // their size is always kLongWordsPerRecord.
- //
- if((flags & kDataRecordIndicatorBits) != 0)
- ++nextIndex;
- //
- // Data records are variable size, though. The physical
- // size is always some multiple of kLongWordsPerRecord, and
- // is encoded in the kDataRecordPhysicalSizeBits.
- //
- else
- {
- nextIndex += ((flags & kDataRecordPhysicalSizeBits) >> (kDataRecordPhysicalSizeShift)) + 1;
- }
-
- //
- // If we get to the end of the group, return -1
- //
- if(nextIndex >= (this->FirstRecordIndex() + kRecordsPerGroup))
- {
- ASSERT(nextIndex == this->FirstRecordIndex() + kRecordsPerGroup);
- nextIndex = -1;
- }
-
- return nextIndex;
- } // TGroupControlObject::NextRecordIndex
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::PreviousRecordIndex
- //--------------------------------------------------------------------------------
- long TGroupControlObject::PreviousRecordIndex(long recordIndex) const
- {
- long previousIndex = recordIndex;
-
- //
- // If we're already at the beginning, there's no point
- // in doing any further calculations.
- //
- if(recordIndex <= this->FirstRecordIndex())
- {
- ASSERT(recordIndex == this->FirstRecordIndex());
- previousIndex = -1;
- }
- else
- {
- long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
-
- //
- // If the data record indicator bits != 0, then this record
- // is a DB record; every DB record is one entry long, so
- // their size is always kLongWordsPerRecord.
- //
- if((flags & kDataRecordIndicatorBits) != 0)
- --previousIndex;
- //
- // Data records are variable size, though. The physical
- // size is always some multiple of kLongWordsPerRecord, and
- // is encoded in the kDataRecordPhysicalSizeBits.
- //
- else
- {
- previousIndex -= (((flags & kPreviousRecordPhysicalSizeBits) >> (kPreviousRecordPhysicalSizeShift)) + 1);
- }
-
- ASSERT(previousIndex >= this->FirstRecordIndex());
- }
-
- return previousIndex;
- } // TGroupControlObject::PreviousRecordIndex
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::BlockEncodedPhysicalSize
- //--------------------------------------------------------------------------------
- long TGroupControlObject::BlockEncodedPhysicalSize(long recordIndex) const
- {
- long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
-
- if((flags & kDataRecordIndicatorBits) != 0)
- return 0;
- else
- return ((flags & kDataRecordPhysicalSizeBits) >> (kDataRecordPhysicalSizeShift));
- } // TGroupControlObject::BlockEncodedPhysicalSize
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::SetBlockEncodedPhysicalSize
- //--------------------------------------------------------------------------------
- void TGroupControlObject::SetBlockEncodedPhysicalSize(long recordIndex, long newEncodedSize)
- {
- long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
-
- Require((this->BlockEncodedPhysicalSize(recordIndex) > 0) && (newEncodedSize > 0));
-
- long newFlags = (flags & ~kDataRecordPhysicalSizeBits) | ((newEncodedSize << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits);
- this->WriteRecordWord(recordIndex, kRecordIDFlagsWord, newFlags);
- } // TGroupControlObject::SetBlockEncodedPhysicalSize
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::LongwordsInRecord
- //--------------------------------------------------------------------------------
- long TGroupControlObject::LongwordsInRecord(long recordIndex) const
- {
- return (this->BlockEncodedPhysicalSize(recordIndex) << kLongWordsPerRecordShift) + kLongWordsPerRecord;
- } // TGroupControlObject::LongwordsInRecord
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::CacheCreatedCursor
- //--------------------------------------------------------------------------------
- void TGroupControlObject::CacheCreatedCursor(long recordIndex, TAbstractRecord* cursor)
- {
- REQUIREVALIDPOINTER(cursor);
- long relativeIndex = this->CalculateRelativeIndex(recordIndex);
- if(fRecordCursors[relativeIndex] != nil)
- {
- if(fRecordCursors[relativeIndex]->HasReference() == false)
- delete fRecordCursors[relativeIndex];
- }
- fRecordCursors[relativeIndex] = cursor;
- } // TGroupControlObject::CacheCreatedCursor
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::NotifyCursorReleased
- //--------------------------------------------------------------------------------
- void TGroupControlObject::NotifyCursorReleased(const TAbstractRecord* cursor)
- {
- REQUIREVALIDPOINTER(cursor);
- long recordIndex = cursor->RecordIndex();
- long relativeIndex = this->CalculateRelativeIndex(recordIndex);
-
- //
- // The first case to worry about is when a new item is created,
- // but the transaction is backed out before the new item becomes
- // a part of the database. In that event, we are in danger of
- // caching an object that represents a free node--and the object
- // may have the wrong class the next time it's referenced. To avoid
- // this problem, we delete free nodes as soon as their references
- // go away.
- //
- if(fRecordCursors[relativeIndex] == cursor)
- {
- //
- //
- //
- if(cursor->ThisRecordIsFree(nil) && (cursor->InTransaction() == false))
- {
- fRecordCursors[relativeIndex] = nil;
- delete (TAbstractRecord*)cursor;
- }
- }
- //
- // The other case to worry about it when an item is freed,
- // someone else may come along and allocate a new record
- // on top of it before all of the old references go away.
- // In that case, the cursor won't be cached in the record
- // cursors cache table any more, and we will wait for
- // the references to go away before we delete the item.
- //
- else
- {
- delete (TAbstractRecord*)cursor;
- }
- } // TGroupControlObject::NotifyCursorReleased
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::GetRecord
- //--------------------------------------------------------------------------------
- TAbstractRecord* TGroupControlObject::GetRecord(long recordIndex)
- {
- long relativeIndex = this->CalculateRelativeIndex(recordIndex);
- TAbstractRecord* cursor = nil;
-
- //
- // Look up the cursor in the table of cached pointers that we keep.
- // If the cursor doesn't exist yet, then ask the document to create it.
- //
- cursor = fRecordCursors[relativeIndex];
- if(cursor == nil)
- {
- //
- // n.b. MakeRecord is going to fill in the
- // apropriate entry in fRecordCursors.
- //
- cursor = DBDocument()->MakeRecord(recordIndex, this->GetRecordWord(recordIndex, 0));
- }
-
- return cursor;
- } // TGroupControlObject::GetRecord
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::RecordData
- //--------------------------------------------------------------------------------
- long* TGroupControlObject::RecordData(long recordIndex) const
- {
- long relativeIndex = this->CalculateRelativeIndex(recordIndex);
-
- if(fRecordData == nil)
- {
- //
- // Const cast away deliberately; we want to provide an
- // interface where 'RecordData' claims to be const, but
- // it may need to call 'ReadRecordGroupFromDisk', which
- // is not const.
- //
- TGroupControlObject* me = (TGroupControlObject*)this;
- me->ReadRecordGroupFromDisk();
- }
-
- return (fRecordData) + (relativeIndex * kLongWordsPerRecord);
- } // TGroupControlObject::RecordData
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::GetRecordWord
- //--------------------------------------------------------------------------------
- long TGroupControlObject::GetRecordWord(long recordIndex, long longwordNumber) const
- {
- long theData = 0;
-
- if((longwordNumber >= 0) && (longwordNumber < this->LongwordsInRecord(recordIndex)))
- {
- theData = (this->RecordData(recordIndex))[longwordNumber];
- }
- else
- Throw(eIndexOutOfRange);
-
- return theData;
- } // TGroupControlObject::GetRecordWord
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::WriteRecordWord
- //
- // This is a private method not to be called by any of the group control object's
- // friends. Any external client of the group control object that wishes to change
- // the contents of a record must make a copy of the record with
- // 'MakeRecordDataCopy', then write the entire record back with 'ChangeRecordData'
- //--------------------------------------------------------------------------------
- void TGroupControlObject::WriteRecordWord(long recordIndex, long longwordNumber, long theData)
- {
- if((longwordNumber >= 0) && (longwordNumber < this->LongwordsInRecord(recordIndex)))
- {
- (this->RecordData(recordIndex))[longwordNumber] = theData;
- fRecordGroupHasChanged = true;
- }
- else
- Throw(eIndexOutOfRange);
- } // TGroupControlObject::WriteRecordWord
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::WriteThroughToTransaction
- //
- // This routine changes bits in a data record both in the persistant (group
- // control object stored) structure and the change image (stored in the update
- // pointer of some transaction). This is currently only necessary in one
- // instance: setting the 'previous block's physical size' field of a data
- // object when splitting a block apart or joining two blocks. The split is
- // done independantly of this transaction, so we need to make sure that the
- // appropriate bits are changed in both places so that the next/previous
- // block links do not become invalid when the transaction is committed.
- //--------------------------------------------------------------------------------
- void TGroupControlObject::WriteThroughToTransaction(long recordIndex, long longwordNumber, long theData, long theMask)
- {
- long currentWordValue = this->GetRecordWord(recordIndex, longwordNumber);
- this->WriteRecordWord(recordIndex, longwordNumber, (currentWordValue & ~theMask) | (theData & theMask));
-
- //
- // If the update cursor exists, inform it that it needs to change some bits
- //
- long relativeIndex = this->CalculateRelativeIndex(recordIndex);
- if(fRecordCursors[relativeIndex] != nil)
- {
- fRecordCursors[relativeIndex]->WriteThroughToTransaction(longwordNumber, theData, theMask);
- }
- }
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::MakeRecordDataCopy
- //--------------------------------------------------------------------------------
- long* TGroupControlObject::MakeRecordDataCopy(long recordIndex)
- {
- long* recordDataAddress = this->RecordData(recordIndex);
- long recordDataLongs = this->LongwordsInRecord(recordIndex);
-
- long* dataCopy = new long[recordDataLongs];
- FailNil(dataCopy);
- CopyMemory(recordDataAddress, dataCopy, recordDataLongs * sizeof(long)); // memcpy(dataCopy, recordDataAddress, recordDataLongs * sizeof(long));
-
- return dataCopy;
- } // TGroupControlObject::MakeRecordDataCopy
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::ChangeRecordData
- //--------------------------------------------------------------------------------
- void TGroupControlObject::ChangeRecordData(long recordIndex, long* newData, Boolean inhibitCompare /* = false */)
- {
- REQUIREVALIDPOINTER(newData);
- long* recordDataAddress = this->RecordData(recordIndex);
- long lengthToCopy = this->LongwordsInRecord(recordIndex) * sizeof(long);
-
- //
- // If the data has changed in any way, then copy the
- // new data back into the group control object and
- // mark that this object has changed.
- //
- // It is worth it to do the compare if it means that
- // we might not need to write this group back to disk;
- // if, however, this group is already dirty, or if
- // the caller knows for certain that the data has
- // changed, then we won't bother to call memcmp.
- //
- // Future: When we start collecting changed record
- // groups into change sets, then it also matters if
- // this record group belongs to the current change set
- // yet, as we would like to avoid merging change sets
- // if the record data hasn't actually changed.
- //
- // ◊disable memory compare for now
- //
- if((inhibitCompare == true) || (this->RecordGroupNeedsSave()) /* || (memcmp(recordDataAddress, newData, lengthToCopy) != 0) */ )
- {
- CopyMemory(newData, recordDataAddress, lengthToCopy); // memcpy(recordDataAddress, newData, lengthToCopy);
- this->RecordGroupHasChanged();
- }
- } // TGroupControlObject::ChangeRecordData
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::RecordDataReference
- //--------------------------------------------------------------------------------
- const TConstDataReference TGroupControlObject::RecordDataReference(long recordIndex, long dataType, long longwordNumber, long numberOfBytes) const
- {
- long* recordDataAddress = this->RecordData(recordIndex);
-
- return TConstDataReference(dataType, (char*)&recordDataAddress[longwordNumber], numberOfBytes);
- } // TGroupControlObject::RecordDataReference
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::TrimBlock
- //
- // Reduce the size of 'blockToTrim' down to 'newEncodedPhysicalSize'.
- // Return the index of the block left over, if any.
- //--------------------------------------------------------------------------------
- long TGroupControlObject::TrimBlock(long blockToTrim, long newEncodedPhysicalSize)
- {
- long currentEncodedPhysicalSize = this->BlockEncodedPhysicalSize(blockToTrim);
- long leftOverPiece = -1;
-
- //
- // Don't trim a block down by just one record size; we
- // always want the leftover peice to be at least 2 records long.
- //
- if((currentEncodedPhysicalSize - newEncodedPhysicalSize) > 1)
- {
- //
- // We are going to make our block smaller by a few (at least two)
- // records; 'encodedSizeLeftAfterTrim' is the size of the
- // record left over after we do so.
- //
- long encodedSizeLeftAfterTrim = (currentEncodedPhysicalSize - newEncodedPhysicalSize) - 1;
-
- //
- // Calculate the record index of the block we're splitting
- // off ('leftOverPiece') and the record index of the block
- // after this one (we'll need to adjust its 'physical size
- // of previous block') link.
- //
- leftOverPiece = blockToTrim + newEncodedPhysicalSize + 1;
- long blockToFixUp = blockToTrim + currentEncodedPhysicalSize + 1;
- ASSERT(leftOverPiece + encodedSizeLeftAfterTrim + 1 == blockToFixUp);
- Require(blockToFixUp <= this->FirstRecordIndex() + kRecordsPerGroup);
-
- //
- // We only split blocks that are free, so we know that we
- // can adjust 'blockToTrim' and 'leftOverPiece' without
- // worrying about a transaction.
- //
- Require((fRecordCursors[this->CalculateRelativeIndex(blockToTrim)] == nil) && (fRecordCursors[this->CalculateRelativeIndex(leftOverPiece)] == nil));
- long trimBlockFlags = this->GetRecordWord(blockToTrim, kRecordIDFlagsWord);
- trimBlockFlags = (trimBlockFlags & ~kDataRecordPhysicalSizeBits) | ((newEncodedPhysicalSize << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits);
- long leftOverBlockFlags = (kFreeDataRecordID) |
- ((encodedSizeLeftAfterTrim << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits) |
- ((newEncodedPhysicalSize << kPreviousRecordPhysicalSizeShift) & kPreviousRecordPhysicalSizeBits);
- this->WriteRecordWord(blockToTrim, kRecordIDFlagsWord, trimBlockFlags);
- this->WriteRecordWord(leftOverPiece, kRecordIDFlagsWord, leftOverBlockFlags);
- this->WriteRecordWord(leftOverPiece, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
- this->WriteRecordWord(leftOverPiece, kPreviousFreeNodeLinkByte, kNilIndex);
-
- //
- // Don't forget, when setting the 'previous block physical size'
- // bits, that the record being changed may have an active change image.
- // If that's the case, then it will be necessary to change both
- // the Group Control Object's previous-physical-size value, and the
- // change image's previous-physical-size value.
- //
- if(blockToFixUp < (this->FirstRecordIndex() + kRecordsPerGroup))
- {
- this->WriteThroughToTransaction(blockToFixUp, kRecordIDFlagsWord, (encodedSizeLeftAfterTrim << kPreviousRecordPhysicalSizeShift), kPreviousRecordPhysicalSizeBits);
- }
- }
-
- return leftOverPiece;
- } // TGroupControlObject::TrimBlock
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::Verify
- //--------------------------------------------------------------------------------
- void TGroupControlObject::Verify(long recordIndex, Boolean verifySubsequent)
- {
- long nextRecord = this->NextRecordIndex(recordIndex);
- long previousRecord = this->PreviousRecordIndex(recordIndex);
- Boolean ourEncodedSizeIsZero = (this->BlockEncodedPhysicalSize(recordIndex) == 0);
-
- //
- // PreviousRecord should only be -1 at the first record in the group
- //
- if(previousRecord == -1)
- {
- if(recordIndex != this->FirstRecordIndex())
- DebugStr("\pRecord other than the first had a nil previous");
- }
- else
- {
- if(this->NextRecordIndex(previousRecord) != recordIndex)
- DebugStr("\pRecord's previous link doesn't point back");
-
- Boolean prevEncodedSizeIsZero = (this->BlockEncodedPhysicalSize(previousRecord) == 0);
- if(prevEncodedSizeIsZero != ourEncodedSizeIsZero)
- DebugStr("\pMixed single and multiple record data blocks in one group");
- }
-
- if(nextRecord != -1)
- {
- if(nextRecord > (FirstRecordIndex() + kRecordsPerGroup))
- DebugStr("\pData record points past the end of a record group");
- else if((nextRecord < this->FirstRecordIndex() + kRecordsPerGroup))
- {
- if(this->PreviousRecordIndex(nextRecord) != recordIndex)
- DebugStr("\pRecord's next link doesn't point back");
- else if(verifySubsequent == true)
- this->Verify(nextRecord, true);
- }
- }
- } // TGroupControlObject::Verify
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::AllocateMemoryForRecordData
- //--------------------------------------------------------------------------------
- void TGroupControlObject::AllocateMemoryForRecordData()
- {
- if(fRecordData == nil)
- {
- fRecordData = new long[kLongWordsPerRecord * kRecordsPerGroup];
- FailNil(fRecordData);
- }
- } // TGroupControlObject::AllocateMemoryForRecordData
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::ReadRecordGroupFromDisk
- //--------------------------------------------------------------------------------
- void TGroupControlObject::ReadRecordGroupFromDisk(TAbstractBackingStore* backingStoreToUse /*= nil*/)
- {
- Require(fRecordGroupHasChanged == false);
- if(fRecordData == nil)
- {
- this->AllocateMemoryForRecordData();
- DBDocument()->ReadRecordRange(fRecordData, this->FirstRecordIndex() * kSingleRecordSize, kLongWordsPerRecord * kRecordsPerGroup * sizeof(long), backingStoreToUse);
- fRecordGroupHasChanged = false;
- }
- } // TGroupControlObject::ReadRecordGroupFromDisk
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::FlushChangesToDisk
- //--------------------------------------------------------------------------------
- void TGroupControlObject::FlushChangesToDisk(TAbstractBackingStore* backingStoreToUse /*= nil*/)
- {
- if((fRecordData != nil) && fRecordGroupHasChanged)
- DBDocument()->WriteRecordRange(fRecordData, this->FirstRecordIndex() * kSingleRecordSize, kLongWordsPerRecord * kRecordsPerGroup * sizeof(long), backingStoreToUse);
- fRecordGroupHasChanged = false;
- } // TGroupControlObject::FlushChangesToDisk
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::PurgeRecordGroupFromMemory
- //--------------------------------------------------------------------------------
- void TGroupControlObject::PurgeRecordGroupFromMemory()
- {
- //
- // NEVER purge if we cannot flush! (Should have a better error number, perhaps)
- //
- if(this->DBDocument()->CanSaveDocument())
- FailErr(-1);
-
- if(fRecordGroupHasChanged)
- this->FlushChangesToDisk();
- delete [] fRecordData;
- fRecordData = nil;
- } // TGroupControlObject::PurgeRecordGroupFromMemory
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::PurgeIfUnreferenced
- //--------------------------------------------------------------------------------
- void TGroupControlObject::PurgeIfUnreferenced()
- {
- #if 0
- Boolean referenced = false;
- long i;
-
- for(i=0;i<kRecordsPerGroup;++i)
- {
- if((fRecordCursors[i] != nil) && (fRecordCursors[i]->HasReference()))
- {
- referenced = true;
- break;
- }
- }
-
- if(referenced == false)
- {
- for(i=0;i<kRecordsPerGroup;++i)
- {
- if(fRecordCursors[i] != nil)
- {
- delete fRecordCursors[i];
- fRecordCursors[i] = nil;
- }
- }
-
- INHERITED::PurgeIfUnreferenced();
- }
- #endif
- } // TGroupControlObject::PurgeIfUnreferenced
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::InitializeNewGroup
- //
- // This routine makes a bunch (64) of new database records. Most (63) of
- // these are immediately pushed onto the db record free list. One of the
- // new records is returned, unlinked and ready to use.
- //--------------------------------------------------------------------------------
- long TGroupControlObject::InitializeNewGroup(long nextFreeNode, long& oneFreeIndex)
- {
- this->AllocateMemoryForRecordData();
-
- //
- // Make kRecordsPerGroup free DB records
- //
- for(long i=this->FirstRecordIndex() + kRecordsPerGroup - 1;i>= this->FirstRecordIndex();--i)
- {
- long* data = this->RecordData(i);
- data[kFreeNodeIDByte] = kFreeDBRecordID;
- data[kFreeNodeLinkByte] = nextFreeNode;
- nextFreeNode = i;
- }
-
- //
- // Mark the first record as not being attached to the free list
- //
- this->RecordData(this->FirstRecordIndex())[kFreeNodeLinkByte] = kFreeRecordNotLinkedToTree;
-
- //
- // The first record in the group is not linked to the free list.
- // The second record in the group is returned as the new first free record.
- //
- oneFreeIndex = this->FirstRecordIndex();
- return this->FirstRecordIndex() + 1;
- } // TGroupControlObject::InitializeNewGroup
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::InitializeNewDataGroup
- //
- // This routine makes one or two free data records. Both are returned unlinked
- // to the tree. One is always of size 'desiredEncodedPhysicalSize'; the other
- // is however big it needs to be to fill out the rest of the record group.
- //--------------------------------------------------------------------------------
- long TGroupControlObject::InitializeNewDataGroup(long desiredEncodedPhysicalSize, long& leftOverBlock)
- {
- //
- // Make one 4K data record
- //
- this->AllocateMemoryForRecordData();
- long* data = this->RecordData(this->FirstRecordIndex());
- data[kFreeNodeIDByte] = kFreeDataRecordID + (63L << kDataRecordPhysicalSizeShift);
- data[kFreeNodeLinkByte] = kFreeRecordNotLinkedToTree;
- data[kPreviousFreeNodeLinkByte] = kNilIndex;
-
- long oneFreeIndex = this->FirstRecordIndex();
- leftOverBlock = this->TrimBlock(oneFreeIndex, desiredEncodedPhysicalSize);
-
- return oneFreeIndex;
- } // TGroupControlObject::InitializeNewDataGroup
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::IndexIsFree
- //--------------------------------------------------------------------------------
- Boolean TGroupControlObject::IndexIsFree(long recordIndex) const
- {
- return ((this->GetRecordWord(recordIndex, kFreeNodeIDByte) & kFreeRecordIDBits) == kFreeRecordIDBits);
- } // TGroupControlObject::IndexIsFree
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::IndexIsFreeAndOnFreeList
- //--------------------------------------------------------------------------------
- Boolean TGroupControlObject::IndexIsFreeAndOnFreeList(long recordIndex) const
- {
- if(this->IndexIsFree(recordIndex))
- return this->GetRecordWord(recordIndex, kFreeNodeLinkByte) != kFreeRecordNotLinkedToTree;
- else
- return false;
- } // TGroupControlObject::IndexIsFreeAndOnFreeList
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::NextFreeIndex
- //--------------------------------------------------------------------------------
- long TGroupControlObject::NextFreeIndex(long afterWhichIndex) const
- {
- //
- // Test to see if 'afterWhichIndex' really is a free node.
- //
- Require(this->IndexIsFree(afterWhichIndex));
- // Require(fRecordCursors[this->CalculateRelativeIndex(afterWhichIndex)] == nil);
-
- //
- // Get the next free index. It might be nice to do some sanity
- // checking on it, too, but we won't do that because the next
- // free node might be in some other group control object.
- //
- long theNextFreeIndex = this->GetRecordWord(afterWhichIndex, kFreeNodeLinkByte);
- if(theNextFreeIndex == kFreeRecordNotLinkedToTree)
- theNextFreeIndex = kNilIndex;
-
- return theNextFreeIndex;
- } // TGroupControlObject::NextFreeIndex
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::PreviousFreeIndex
- //--------------------------------------------------------------------------------
- long TGroupControlObject::PreviousFreeIndex(long beforeWhichIndex) const
- {
- //
- // Test to see if 'beforeWhichIndex' really is a free node.
- //
- Require(this->IndexIsFree(beforeWhichIndex));
- // Require(fRecordCursors[this->CalculateRelativeIndex(beforeWhichIndex)] == nil);
-
- //
- // The first free list is unique in that it does not maintain
- // previous links.
- //
- long freeList = this->FreeListToUse(beforeWhichIndex);
- Require(freeList > 0);
-
- //
- // Get the next free index. It might be nice to do some sanity
- // checking on it, too, but we won't do that because the previous
- // free node might be in some other group control object.
- //
- long thePreviousIndex = kNilIndex;
- if(this->IndexIsFreeAndOnFreeList(beforeWhichIndex))
- thePreviousIndex = this->GetRecordWord(beforeWhichIndex, kPreviousFreeNodeLinkByte);
-
- return thePreviousIndex;
- } // TGroupControlObject::PreviousFreeIndex
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::RecordCursorStale
- //--------------------------------------------------------------------------------
- void TGroupControlObject::RecordCursorStale(long staleRecord)
- {
- //
- // Smash the reference to any cursor to the free node
- //
- long relativeIndex = this->CalculateRelativeIndex(staleRecord);
- if(fRecordCursors[relativeIndex] != nil)
- {
- //
- // Free the cursor if we can
- //
- TAbstractRecord* freeCursor = fRecordCursors[relativeIndex];
- fRecordCursors[relativeIndex] = nil;
-
- //
- // If the cursor is unreferenced, this routine
- // will delete it directly. If there are references,
- // then the cursor will be marked for deletion
- // as soon as the last reference is released.
- //
- freeCursor->DisposeRecordIfUnreferenced();
- }
- } // TGroupControlObject::RecordCursorStale
-
- //--------------------------------------------------------------------------------
- // TGroupControlObject::MergeFreeBlocks
- //--------------------------------------------------------------------------------
- long TGroupControlObject::MergeFreeBlocks(long firstRecord, long recordToMerge)
- {
- long blockToFixUp = this->NextRecordIndex(recordToMerge);
- ASSERT(recordToMerge == this->NextRecordIndex(firstRecord));
-
- //
- // First, remove the records from their free lists, if they're on one
- //
- this->DBDocument()->RemoveFromFreeList(firstRecord);
- this->DBDocument()->RemoveFromFreeList(recordToMerge);
-
- //
- // Next, increase the physical size of 'firstRecord' to
- // encompass 'recordToMerge'.
- //
- long encodedMergeSize = this->BlockEncodedPhysicalSize(recordToMerge);
- long originalEncodedSize = this->BlockEncodedPhysicalSize(firstRecord);
- Require((encodedMergeSize > 0) && (originalEncodedSize > 0));
- long newEncodedSize = originalEncodedSize + encodedMergeSize + 1;
- this->SetBlockEncodedPhysicalSize(firstRecord, newEncodedSize);
- ASSERT(newEncodedSize == this->FreeListToUse(firstRecord));
-
- //
- // Finally, adjust the block after 'recordToMerge' so
- // that it has the correct previous physical size entry.
- // (blockToFixUp will be kNilIndex if 'recordToMerge' is
- // the last block in the group).
- //
- if(blockToFixUp != kNilIndex)
- {
- ASSERT(blockToFixUp == this->NextRecordIndex(firstRecord));
- this->WriteThroughToTransaction(blockToFixUp, kRecordIDFlagsWord, (newEncodedSize << kPreviousRecordPhysicalSizeShift), kPreviousRecordPhysicalSizeBits);
- }
-
- //
- // Return the free list that the new record would be pushed onto.
- //
- return this->FreeListToUse(firstRecord);
- } // TGroupControlObject::MergeFreeBlocks
-
-